.intel_syntax noprefix
#include "realthunk.h"

.set CR0_PE_ON, 1
.set GDT32_CTHUNKSEG_SEL, 24
.set GDT32_DTHUNKSEG_SEL, 32
.set GDT32_CSEG_SEL, 8
.set GDT32_DSEG_SEL, 16

.globl RealThunk
RealThunk:
  .code32

  mov eax, offset protected_mode_stack
  mov DWORD PTR [eax], esp
  mov DWORD PTR [eax + 4], ebp

  # 0. Copy parameters to memory within 16bit 0xFFFF limit segment
  mov edi, 0xFEFF # Destination
  mov esi, [esp + 4] # params (address of parameters)
  mov ecx, [esp + 8] # size (size of parameters)
  rep movsb

  # 1. Disable interrupts
  cli

  # 2. If paging is enabled, perform the following operations:
  #   * Transfer program control to linear addresses that are identity mapped to physical addresses (that is, linear addresses equal physical addresses).
  #   * Insure that the GDT and IDT are in identity mapped pages.
  #   * Clear the PG bit in the CR0 register.
  #   * Move 0H into the CR3 register to flush the TLB.
  mov eax, 0
  mov cr3, eax

  # 3. Transfer program control to a readable segment that has a limit of 64 KBytes (FFFFH). This operation loads the CS register with the segment limit required in real-address mode.
  jmp GDT32_CTHUNKSEG_SEL:ReloadCS
ReloadCS: # 16-bit protected mode
  .code16

  # 4. Load segment registers SS, DS, ES, FS, and GS with a selector for a descriptor containing the following values, which are appropriate for real-address mode:
  #   * Limit = 64 KBytes (0FFFFH)
  #   * Byte granular (G = 0)
  #   * Expand up (E = 0)
  #   * Writable (W = 1)
  #   * Present (P = 1)
  #   * Base = any value
  # The segment registers must be loaded with non-null segment selectors or the segment registers will be unusable in real-address mode. Note that if the segment registers are not reloaded, execution continues using the descriptor attributes loaded during protected mode.
  mov ax, GDT32_DTHUNKSEG_SEL
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  mov ss, ax

  # 5. Execute an LIDT instruction to point to a real-address mode interrupt table that is within the 1-MByte real-address mode address range.
  lidt [real_idt]

  # 6. Clear the PE flag in the CR0 register to switch to real-address mode.
  mov eax, cr0
  xor eax, CR0_PE_ON
  mov cr0, eax
 
  # 7. Execute a far JMP instruction to jump to a real-address mode program. This operation flushes the instruction queue and loads the appropriate base-address value in the CS register.
  jmp 0:Realmode
Realmode:

  # 8. Load the SS, DS, ES, FS, and GS registers as needed by the real-address mode code. If any of the registers are not going to be used in real-address mode, write 0s to them.
  xor ax, ax
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  mov ss, ax
  mov sp, 0x8400
  mov bp, sp

  # 9. Execute the STI instruction to enable maskable hardware interrupts and perform the necessary hardware operation to enable NMI interrupts.
  sti

  #
  # Real thunk switch
  #
RealThunkSwitch:
  mov si, 0xFEFF
  mov al, ds:[si]

  movzx cx, al
  shl cx, 1
  mov di, offset real_thunk_switch_table
  add di, cx
  mov ax, ds:[di]

  # Jump to respective function
  jmp ax

SetVideoMode:
  mov ah, 0x00
  mov al, ds:[si + 1]
  int 0x10
  jmp EndRealThunkSwitch

GetVideoMode:
  mov ah, 0x0F
  int 0x10
  movzx eax, al
  jmp EndRealThunkSwitch

GetVideoModeAvailability:
  mov ah, 0x12
  mov bl, 0xA0
  mov al, ds:[si + 1]
  int 0x10
  and ah, 1
  movzx eax, ah
  jmp EndRealThunkSwitch

PutPixel:
  mov ah, 0x0C
  mov bh, 0x0 # Page number
  mov al, ds:[si + 1] # Color
  mov cx, ds:[si + 2] # X coordinate
  mov dx, ds:[si + 4] # Y coordinate
  int 0x10
  jmp EndRealThunkSwitch

PixelAt:
  mov ah, 0x0D
  mov bh, 0x0 # Page number
  mov cx, ds:[si + 1] # X coordinate
  mov dx, ds:[si + 3] # Y coordinate
  int 0x10
  movzx eax, al
  jmp EndRealThunkSwitch

Read1Sector:
  mov eax, DWORD PTR ds:[si+1]
  mov ecx, DWORD PTR ds:[si+5]
  mov si, offset sector_dap
  mov DWORD PTR ds:[si+8], eax
  mov DWORD PTR ds:[si+12], ecx

  mov dl, 0x80
  mov ah, 0x42
  int 0x13
  movzx eax, ah
  jmp EndRealThunkSwitch

Write1Sector:
  mov eax, DWORD PTR ds:[si+1]
  mov ecx, DWORD PTR ds:[si+5]
  mov si, offset sector_dap
  mov DWORD PTR ds:[si+8], eax
  mov DWORD PTR ds:[si+12], ecx

  mov dl, 0x80
  mov ah, 0x43
  int 0x13
  movzx eax, ah
  jmp EndRealThunkSwitch

EndRealThunkSwitch:

  # Change back to protected mode
  cli
  cld

  # Clear 32bit registers
  xor bx, bx
  mov ss, bx
  mov ds, bx
  mov es, bx
  mov fs, bx
  mov gs, bx

  # Set PE bit
  mov ebx, cr0
  or ebx, CR0_PE_ON
  mov cr0, ebx

  jmp GDT32_CSEG_SEL, RealThunk.32
RealThunk.32:
  .code32

  mov bx, GDT32_DSEG_SEL
  mov ds, bx
  mov es, bx
  mov fs, bx
  mov gs, bx
  mov ss, bx

  mov edi, offset protected_mode_stack
  mov esp, DWORD PTR [edi]
  mov ebp, DWORD PTR [edi + 4]

  ret

.balign
protected_mode_stack:
  .long 0xDEADBEEF # esp
  .long 0xDEADBEEF # ebp

.balign 4
real_idt:
  .word 0x3FF # 256 entries, 4b each = 1K
  .long 0 # Real Mode IVT @ 0x0000

.balign 4
sector_dap:
  .byte 0x10               # Size of packet
  .byte 0                  # Unused
  .word 1                  # Num sectors
  .word 0x1FC4             # Offset
  .word 0x0                # Segment
  .quad 0xABCDEFABCDEFABCD # Sector start

.balign 4
real_thunk_switch_table:
  .word SetVideoMode
  .word GetVideoMode
  .word GetVideoModeAvailability
  .word PutPixel
  .word PixelAt
  .word Read1Sector
  .word Write1Sector
